/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package core;

import java.math.BigDecimal;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

/**
 *
 * @author Sakis Vtdk
 */
public class MysqlScriptGenerator extends SqlScriptGenerator
{
	
	public MysqlScriptGenerator()
	{
		this.dummyFuncPopulateHash();
	}

	/**
	 * Creates an sql script for all the changes in the changesLogger
	 * 
	 * @return The sql script
	 */
	@Override
	public String getSqlScript()
	{
		String sqlScript = "";
		
		ChangesLogger changesLogger = ChangesLogger.getChangesLogger();

		
		for(Change change : changesLogger.getChanges())
		{
			if(change.getObjectChanged().equals(Change.ObjectChanged.COLUMN.toString()))
			{
				updateTmpColumns(change);
			}
			sqlScript += createSql(change) +"\n";
		}

		this._tmpColumns.clear();

		return sqlScript;
	}

	/**
	 * Creates an sql script for the changes passed as arguments
	 * 
	 * @param changeSet A list of changes for which a script will be generated
	 * @return The sql script for these changes
	 */
	@Override
	public String getSqlScript(ArrayList<Change> changeSet)
	{
		String sqlScript = "";

		for(Change change : changeSet)
		{
			if(change.getObjectChanged().equals(Change.ObjectChanged.COLUMN.toString()))
			{
				updateTmpColumns(change);
			}
			sqlScript += createSql(change) +"\n";
		}
		
		this._tmpColumns.clear();
		return sqlScript;
	}

	/**
	 * Creates the sql script for the given <code>Change</code> object
	 * 
	 * @param change the object for which the sql will be created
	 * @return The sql script to perform that change
	 */
	private String createSql(Change change)
	{
		String commandName = generateCommandName(change);
		String script = "";

		//search the commandName in the hashmap
		if(this._commands.containsKey(commandName))
		{
			log.debug("Change: "+ commandName);
			script = this._commands.get(commandName);

			script = replacePlaceholders(script, change);
		}

		return script;
	}

	
	/**
	 * Replaces the placeholders (text between { }) with the actual values
	 * 
	 * @param sqlCommand The sqlCommand containing the placeholders
	 * @param change The change based on which the placeholders will be replaced
	 * @return The sqlCommand after the replacement has taken place
	 */
	private String replacePlaceholders(String sqlCommand, Change change)
	{
		String myStr = "";
		ArrayList<String> placeholders = extractPlaceholders(sqlCommand);
		ArrayList<String> placeholdersValues = findPlaceholdersValues(placeholders, change);
	
		if(placeholders.size() == placeholdersValues.size())
		{
			for(int i=0; i < placeholders.size(); i++ )
			{
				Pattern pattern = Pattern.compile("\\{"+ placeholders.get(i) +"\\}");
				Matcher matcher = pattern.matcher(sqlCommand);	

				sqlCommand = matcher.replaceAll(placeholdersValues.get(i));
			}
		}
			
		log.debug(sqlCommand);
		
		return sqlCommand;
	}

	/**
	 * Based on the <code>change</code>, the method retrieves the correct value
	 * for each element in the <code>placeholders</code>
	 * 
	 * @param placeholders A list of the placeholders found in a command
	 * @param change The change based on which the values of the placeholders will be retrieved
	 * @return An ArrayList of the values for each placeholder. The correspondence is 1-1 between the
	 * indexes of the ArrayLists
	 */
	private ArrayList<String> findPlaceholdersValues(ArrayList<String> placeholders, Change change)
	{
		ArrayList<String> placeholdersValues = new ArrayList<String>();

		Database db = Database.getDatabase();
		DatabaseTable tbl = null;
		TableColumn col = null;

		//if the change is NOT for a table
		if(change.getParentId() != -1)
		{
			tbl = db.getTable(change.getParentId());
			col = tbl.getColumn(change.getId());
		}
		else
		{ 
			tbl = db.getTable(change.getId());
		}
		
		//for each placeholder get the right value
		for(String placeholder : placeholders)
		{
			if(placeholder.equalsIgnoreCase("tbl_name"))
			{
				if(change.getObjectChanged().equals(Change.ObjectChanged.COLUMN.toString()))
				{
					placeholdersValues.add(change.getParentName());
				}
				else
				{
					placeholdersValues.add(change.getName());
				}
			}
			else if(placeholder.equalsIgnoreCase("col_name"))
			{
				if(change.getObjectChanged().equals(Change.ObjectChanged.COLUMN.toString()))
				{
					placeholdersValues.add(change.getName());
				}
				//placeholdersValues.add(tbl.getColumn(change.getId()).getName());	
			}
			else if(placeholder.equalsIgnoreCase("old_col_name") || placeholder.equalsIgnoreCase("old_tbl_name"))
			{
				placeholdersValues.add(change.getOldValue());
			}
			else if(placeholder.equalsIgnoreCase("new_col_name") || placeholder.equalsIgnoreCase("new_tbl_name"))
			{
				placeholdersValues.add(change.getNewValue());
			}
			else if(placeholder.equals("col_definition"))
			{
				placeholdersValues.add(this.getDefinition(change));
			}
			
			log.debug(placeholder +" => "+ placeholdersValues.get(placeholdersValues.size()-1));
		}
		
		return placeholdersValues;
	}

	/**
	 * Method returns the definition of the given {@link TableColumn}
	 * 
	 * @param col
	 * @return The definition of the column
	 */
	private String getDefinition(Change colChange)
	{

		TableColumn col = getColumn(colChange);

		String def = "";

		def += col.getDataType(true);

		if(!col.getDefaultValue().equalsIgnoreCase(""))
		{
			def += " DEFAULT_VALUE "+ col.getDefaultValue();
		}
		if(TableColumn.DataType.valueOf(col.getDataType(false)).isAutoIncrementable() && col.isAutoIncrement())
		{
			def += " AUTO_INCREMENT";
		}
		
		//if(col.isPrimaryKey())
		//{
			//def += " PRIMARY KEY";
		//}

		return def;
	}


	/**
	 * Extracts the placeholders from a string. A placeholder is defined as a string 
	 * between curly braces. This method extracts <b>only</b> the string and <b>not</b> 
	 * the surrounding curly braces
	 * 
	 * @param string The string to be searched for placeholders
	 * @return An ArrayList containing the found placeholders
	 */
	private ArrayList<String> extractPlaceholders(String string)
	{
		ArrayList<String> placeHolders = new ArrayList<String>();

		//create the regular expression that will match the placeholders
		Pattern pattern = Pattern.compile("\\{([a-zA-Z_^}]+)\\}");
		Matcher matcher = pattern.matcher(string);

		//get all the matches in the string
		while(matcher.find())
		{
			String tmpStr = matcher.group(1);
			placeHolders.add(tmpStr);
			log.debug("Placeholder found: "+ tmpStr);
		}

		return placeHolders;
	}
	
	/**
	 * Parses the change and creates the commandName to be searched
	 * 
	 * @param change
	 * @return The commandName for which the script will be generated
	 */
	private String generateCommandName(Change change)
	{
		String command = "";

		command += change.getType();
		command += "_";
		command += change.getObjectChanged();

		if(change.getType().equalsIgnoreCase("UPDATE"))
		{
			command += "_";
			command += change.getWhatChanged();
		}

		//TODO: this should be fixed
		if(change.getType().equalsIgnoreCase("UPDATE") &&
			change.getWhatChanged().equals(Change.WhatChanged.PRIMARY_KEY.toString()) && 
			change.getNewValue().equals(Boolean.FALSE.toString()))
		{
			command = "DELETE_COLUMN_PRIMARY_KEY";
		}

		command = command.toUpperCase(Locale.ENGLISH);

		log.trace("Command built: "+ command);

		return command;
	}

	/**
	 * for testing purposes, it will be replaced by a function reading these from a file or something
	 */
	private void dummyFuncPopulateHash()
	{
		this._commands.put("CREATE_TABLE", "CREATE TABLE {tbl_name} ;");
		this._commands.put("CREATE_COLUMN", "ALTER TABLE {tbl_name} ADD COLUMN {col_name} {col_definition} ;");
		this._commands.put("UPDATE_COLUMN_NAME", "ALTER TABLE {tbl_name} CHANGE COLUMN {old_col_name} {new_col_name} {col_definition} ;");
		this._commands.put("UPDATE_TABLE_NAME", "RENAME TABLE {old_tbl_name} TO {new_tbl_name} ;");
		this._commands.put("UPDATE_COLUMN_DATA_TYPE", "ALTER TABLE {tbl_name} MODIFY COLUMN {col_name} {col_definition} ;");
		this._commands.put("UPDATE_COLUMN_DATA_SIZE", "ALTER TABLE {tbl_name} MODIFY COLUMN {col_name} {col_definition} ;");
		this._commands.put("UPDATE_COLUMN_DEFAULT_VALUE", "ALTER TABLE {tbl_name} MODIFY COLUMN {col_name} {col_definition} ;");
		this._commands.put("UPDATE_COLUMN_AUTO_INCREMENT", "ALTER TABLE {tbl_name} MODIFY COLUMN {col_name} {col_definition} ;");
		this._commands.put("UPDATE_COLUMN_PRIMARY_KEY", "ALTER TABLE {tbl_name} ADD PRIMARY KEY ( {col_name} ) ;");
		this._commands.put("DELETE_COLUMN_PRIMARY_KEY", "ALTER TABLE {tbl_name} DROP PRIMARY KEY ;");
		this._commands.put("DELETE_TABLE", "DROP TABLE {tbl_name} ;");
		this._commands.put("DELETE_COLUMN", "ALTER TABLE {tbl_name} DROP COLUMN {col_name} ;");
	}
	
	

	/**
	 * The hashtMap containing the commands that will be used to create the sql commands
	 */
	private HashMap<String, String> _commands = new HashMap<String, String>();
	private static Logger log = Logger.getLogger(MysqlScriptGenerator.class);
	
}
